//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using System.IO;
using EffiProz.Core.Lib;
using EffiProz.Core.ScriptIO;
using EffiProz.Core.Persist;
using EffiProz.Core.Errors;



namespace EffiProz.Core
{

    // tony_lai@users 20020820 - patch 595099 - user define PK name

    /**
     * Subclass of Table to handle TEXT data source. <p>
     *
     *:Table to provide the notion of an SQL base table object whose
     * data is read from and written to a text format data file.
     *
     * @author sqlbob@users (RMP)
     * @version    1.8.0
     */
    public class TextTable : Table
    {

        private String dataSource = "";
        private bool isReversed = false;
        private bool _isConnected = false;

        //    TextCache cache;

        /**
         * Constructs a new TextTable from the given arguments.
         *
         * @param db the owning database
         * @param name the table's HsqlName
         * @param type code (normal or temp text table)
         */
        public TextTable(Database db, QNameManager.QName name, int type)
            : base(db, name, type)
        {

        }

        public bool isConnected()
        {
            return _isConnected;
        }

        /**
         * connects to the data source
         */
        public void connect(Session session)
        {
            connect(session, _isReadOnly);
        }

        /**
         * connects to the data source
         */
        private void connect(Session session, bool withReadOnlyData)
        {

            // Open new cache:
            if ((dataSource.Length == 0) || _isConnected)
            {

                // nothing to do
                return;
            }

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);

            this.store = store;

            DataFileCache cache = null;

            try
            {
                cache = (TextCache)database.logger.openTextFilePersistence(this,
                        dataSource, withReadOnlyData, isReversed);

                store.setCache(cache);

                // read and insert all the rows from the source file
                Row row = null;
                int nextpos = 0;

                if (((TextCache)cache).ignoreFirst)
                {
                    nextpos += ((TextCache)cache).readHeaderLine();
                }

                while (true)
                {
                    row = (Row)store.get(nextpos, false);

                    if (row == null)
                    {
                        break;
                    }

                    Object[] data = row.getData();

                    nextpos = row.getPos() + row.getStorageSize();

                    ((RowAVLDiskData)row).setNewNodes();
                    systemUpdateIdentityValue(data);
                    enforceRowConstraints(session, data);

                    for (int i = 0; i < indexList.Length; i++)
                    {
                        indexList[i].insert(null, store, row);
                    }
                }
            }
            catch (Exception t)
            {
                int linenumber = cache == null ? 0
                                               : ((TextCache)cache)
                                                   .getLineNumber();

                clearAllData(session);

                if (cache != null)
                {
                    database.logger.closeTextCache(this);
                    store.release();
                }

                // everything is in order here.
                // At this point table should either have a valid (old) data
                // source and cache or have an empty source and null cache.
                throw Error.error(t, ErrorCode.TEXT_FILE, 0, new Object[] {
                t.Message, linenumber
            });
            }

            _isConnected = true;
            _isReadOnly = withReadOnlyData;
        }

        /**
         * disconnects from the data source
         */
        public void disconnect()
        {

            this.store = null;

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);

            store.release();

            _isConnected = false;
        }

        /**
         * This method does some of the work involved with managing the creation
         * and openning of the cache, the rest is done in Log.java and
         * TextCache.java.
         *
         * Better clarification of the role of the methods is needed.
         */
        private void openCache(Session session, String dataSourceNew,
                               bool isReversedNew, bool isReadOnlyNew)
        {

            String dataSourceOld = dataSource;
            bool isReversedOld = isReversed;
            bool isReadOnlyOld = _isReadOnly;

            if (dataSourceNew == null)
            {
                dataSourceNew = "";
            }

            disconnect();

            dataSource = dataSourceNew;
            isReversed = (isReversedNew && dataSource.Length > 0);

            try
            {
                connect(session, isReadOnlyNew);
            }
            catch (CoreException e)
            {
                dataSource = dataSourceOld;
                isReversed = isReversedOld;

                connect(session, isReadOnlyOld);

                throw e;
            }
        }

        /**
         * High level command to assign a data source to the table definition.
         * Reassigns only if the data source or direction has changed.
         */
        public void setDataSource(Session session, String dataSourceNew,
                                     bool isReversedNew, bool createFile)
        {

            if (getTableType() == Table.TEMP_TEXT_TABLE)
            {
                ;
            }
            else
            {
                session.getGrantee().checkSchemaUpdateOrGrantRights(
                    getSchemaName().name);
            }

            dataSourceNew = dataSourceNew.Trim();

            if (createFile && FileUtil.GetDefaultInstance().exists(dataSourceNew))
            {
                throw Error.error(ErrorCode.TEXT_SOURCE_EXISTS, dataSourceNew);
            }

            //-- Open if descending, direction changed, file changed, or not connected currently
            if (isReversedNew || (isReversedNew != isReversed)
                    || !dataSource.Equals(dataSourceNew) || !_isConnected)
            {
                openCache(session, dataSourceNew, isReversedNew, _isReadOnly);
            }

            if (isReversed)
            {
                _isReadOnly = true;
            }
        }

        public String getDataSource()
        {
            return dataSource;
        }

        public bool isDescDataSource()
        {
            return isReversed;
        }

        public void setHeader(String header)
        {

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);
            TextCache cache = (TextCache)store.getCache();

            if (cache != null && cache.ignoreFirst)
            {
                cache.setHeader(header);

                return;
            }

            throw Error.error(ErrorCode.TEXT_TABLE_HEADER);
        }

        public String getHeader()
        {

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);
            TextCache cache = (TextCache)store.getCache();
            String header = cache == null ? null
                                             : cache.getHeader();

            return header == null ? null
                                  : StringConverter.toQuotedString(header, '\'',
                                  true);
        }

        /**
         * Used by INSERT, DELETE, UPDATE operations. This class will return
         * a more appropriate message when there is no data source.
         */
       public override void checkDataReadOnly()
        {

            if (dataSource.Length == 0)
            {
                throw Error.error(ErrorCode.TEXT_TABLE_UNKNOWN_DATA_SOURCE);
            }

            if (_isReadOnly)
            {
                throw Error.error(ErrorCode.DATA_IS_READONLY);
            }
        }

        public override bool isDataReadOnly()
        {
            return !isConnected() || base.isDataReadOnly() ||
                store.getCache().isDataReadOnly();
        }

        public override void setDataReadOnly(bool value)
        {

            if (!value)
            {
                if (isReversed)
                {
                    throw Error.error(ErrorCode.DATA_IS_READONLY);
                }

                if (database.isFilesReadOnly())
                {
                    throw Error.error(ErrorCode.DATABASE_IS_READONLY);
                }
            }

            _isReadOnly = value;
        }

        public override bool isIndexCached()
        {
            return false;
        }

        public void setIndexRoots(String s)
        {

            // do nothing
        }

        public String getDataSourceDDL()
        {

            String dataSource = getDataSource();

            if (dataSource == null)
            {
                return null;
            }

            bool isDesc = isDescDataSource();
            StringBuilder sb = new StringBuilder(128);

            sb.Append(Tokens.T_SET).Append(' ').Append(Tokens.T_TABLE).Append(' ');
            sb.Append(getName().getSchemaQualifiedStatementName());
            sb.Append(' ').Append(Tokens.T_SOURCE).Append(' ').Append('\'');
            sb.Append(dataSource);
            sb.Append('\'');

            if (isDesc)
            {
                sb.Append(' ').Append(Tokens.T_DESC);
            }

            return sb.ToString();
        }

        /**
         * Generates the SET TABLE <tablename> SOURCE HEADER <string> statement for a
         * text table;
         */
        public String getDataSourceHeader()
        {

            String header = getHeader();

            if (header == null)
            {
                return null;
            }

            StringBuilder sb = new StringBuilder(128);

            sb.Append(Tokens.T_SET).Append(' ').Append(Tokens.T_TABLE).Append(' ');
            sb.Append(getName().getSchemaQualifiedStatementName());
            sb.Append(' ').Append(Tokens.T_SOURCE).Append(' ');
            sb.Append(Tokens.T_HEADER).Append(' ');
            sb.Append(header);

            return sb.ToString();
        }

        /**
         * Adds commitPersistence() call
         */
        public override void insertData(PersistentStore store, Object[] data)
        {

            Row row = (Row)store.getNewCachedObject(null, data);

            store.indexRow(null, row);
            store.commitPersistence(row);
        }
    }
}
